/*
** part of smse2k's emulation library
** by unknownfile, 2008, 2009
**
** Hardware.cpp - Emulated hardware stuff.
**
** TODO:
** - Fix scheduler and move it out to Hardware_Scheduler.cpp
*/

#include "smse2kincludes.h"
#include <stdlib.h>
#include <conio.h>

// Default values for the scheduler
#define SCHED_RUNTIME ( 1.0 / 60.0 )
#define SCHED_STEP	  ( 1.0 / 60.0 )

// Init trap to prevent bad memory
CHardware::CHardware(int srate) {
	int i;
	
	// Timers have no additional resources... we can just delete them
	for (i=0;i<HARDWARE_MAX_TIMERS;i++) {
		this->timer[i] = 0;
		
	}

	// Now we have to do device-specific cleanups
	for (i=0;i<HARDWARE_MAX_CPUS;i++) {
		cpu[i] = 0;
		cpu_cycles_to_run   [i] = 0;
		cpu_cycles_done [i] = 0;
		cpu_idle_frame[i] = 0;
	}


	cpucount = 0;
	timercount = 0;
	isInStreamMode = 0;
	speakerCount = 0;  // Speaker count, set up by sound devices.
	this->srate = srate;				// Sample rate
	nsrate = 0;				// System's native sample rate (sample rate usually shouldn't exceed this, if we can, we resample stuff)
	uptime = 0.0;		// System run time in seconds
	

	sched_runtime = SCHED_RUNTIME;
	sched_step	  = SCHED_STEP;

	// Create the mixer
	mixer = new CMixer(this, srate);

	OpenConsole();
	Msg("smse2k emulation engine init\n");
}


/*********************************************************************
**********************************************************************

		    DEVICE INITIALIZATION STUFF

**********************************************************************
*********************************************************************/

/*
** addCPU: Adds a CPU, returns the ID of the CPU if OK, -1 otherwise.
*/
int CHardware::AddCpu(ICpu* cpu) {
	this->cpu[cpucount++] = cpu;
	return cpucount;
}

// AddSound deprecated, stuff goes through the mixer and buffers nnow
int CHardware::AddSound(ISoundDevice* dev) {
	return -1;
}

int CHardware::AddTimer(CTimer* timer) {
	Msg("Added new timer!\n");
	this->timer[timercount++] = timer;
	return timercount;
}

/*******************************************************************
						Scheduler and Other Stuff

The order is this...
- UpdateScheduler() - Compute the next CPU cycles.
- RunCPUFrame()     - Runs the CPU cycles that we scheduled.
- RunSoundFrame()   - Updates a sample of sound.
- RunTimerFrame()   - Runs a timer frame.

Then stuff is resampled accordingly.

*******************************************************************/

void CHardware::ChangeScheduler(double framelen, double step) {

	this->sched_runtime = framelen;
	this->sched_step	= step;

}

//
// RunCPUFrame: Runs CPUs and timers for one frame.
//
void CHardware::RunCPUFrame() {
}

void CHardware::RunSoundFrame() {
}

void CHardware::RunTimerFrame() {
}

/*
** RunAudioFrames: Runs emulation for one frame.
** A lot of this used to be assigned to different calls but they've all been
** integrated here.
**
** This scheduler is incredibly lame. MAME sends a variable amount of cycles through, the
** smse2k scheduler just shits out the same amount of cycles. This does work nicely for systems
** depending on IRQs, but when it comes to arcade games (and other stuff relying on real-time audio
** generation) this scheduler simply doesn't work (Parodius and Gradius III are examples).
**
** THE MAME SCHEDULER AT WORK
** Requested 14157 cycles from Z80
** Requested 14336 cycles from Z80
** Requested 14157 cycles from Z80
** Requested 4980 cycles from Z80
** Requested 4802 cycles from Z80
** Requested 9351 cycles from Z80
**
** This will all be moved out to Hardware_Scheduler.cpp later.
*/


unsigned int CHardware::RunAudioFrames(short* buf, UINT32 bufsize) {
	
	// For debug output
	char debugbuf[256];

	// Reset the mixer
	mixer->Reset( sched_runtime );	

	// Check if we are actually running CPUs. If we aren't, display a debug message and
	// tell the caller that we didn't run anything.
	if (!cpucount) {
			Msg("ERROR: No CPUs present, use NullCPU if your hardware doesn't use a CPU\n");
			return 0; // We didn't run, go away
	}

	// Calculate when the next timer is set to fire away

#if 0
	double time_to_next_timer = 65536.0;
	if (timercount) {
		for (int i = 0; i < timercount; i++)
			if ( timer[i]->GetTimeLeft() < time_to_next_timer && timer[i]->GetTimeLeft() > 0.0) {
				Msg("Changing next timer fire to %f where i = %d (old was %f)\n",timer[i]->GetTimeLeft(),i,time_to_next_timer);
				time_to_next_timer = timer[i]->GetTimeLeft();

			}
		
	} else {
		time_to_next_timer = sched_step;
	}
#else
#define time_to_next_timer sched_step
#endif
	//Msg("About to run for %f second(s)\n", time_to_next_timer);

	// Compute cycles for the first CPU
	cpu_cycles_to_run[0] = cpu[0]->GetClock() * time_to_next_timer;
	//Msg("CPU 0 will get %d cycles this frame\n",cpu_cycles_to_run[0]);

	// Initialize runtime
	double runtime = 0.0;

	// Run the CPUs and timers, forcing everything to run in sync.
	//while( runtime < sched_runtime ) {
		// Run the CPUs
		for (int x = 0; x < cpucount; x++) {

					// If this isn't the first CPU we've run, check the runtime of the previous CPU and convert that
					// to cycles. Otherwise, just continue on with what we have.
					if ( x > 0)
							cpu_cycles_to_run[x] = cpu[x]->GetClock() * cpu_runtime[x - 1];
					

					// Execute the frame.
					cpu_cycles_done[x] = cpu[x]->Exec(cpu_cycles_to_run[x]);
					cpu_cycles_to_run[x] -= (cpu_cycles_done[x] - cpu_cycles_done[x]); 	
					// Convert the cycles back to seconds and check to see if we executed for how long we wanted to.
					cpu_runtime[x] = (double)cpu_cycles_done[x] / (double)cpu[x]->GetClock();
					//Msg("CPU %d ran for %f seconds ( we specified %f )\n",x,cpu_runtime[x],time_to_next_timer);
		}


		// Estimate actual runtime based on stuff we got
		double average_time = 0.0;
		int    average_count = 0;
		for (int x = 0; x < cpucount; x++) {
			average_count++;
			average_time += cpu_runtime[x];
		}

		// again, this _is_ an estimate
		double average_time_computed = (average_time) / ((double)(average_count));

		// Run timers for however long we just executed
		for (int y = 0; y < timercount; y++) {
			timer[y]->Run(average_time_computed);
		}

		// Advance the mixer!
		mixer->Advance( uptime, average_time_computed );
		
		runtime += average_time_computed; // Update the run time for this frame
		uptime  += average_time_computed; // Update the system uptime		
	//} 

	// Debug stuff
	SetConTitle("smse2k - runtime %f, uptime %f",runtime,uptime);

	// Move the buffer
	return mixer->Buffer(buf, bufsize); 
}

double CHardware::RunAudioFramesUntilSound() {
	return 0; // unimplemented
}

/*********************************************************************
**********************************************************************

	       HARDWARE INITIALIZATION/DEINITIALIZATION

**********************************************************************
*********************************************************************/

void CHardware::SetOutput(unsigned int sampleRate) {
	this->srate = sampleRate;
}

/*
** reset: Hard resets emulated devices.
*/

void CHardware::Reset(void) {
		int i=0;
		for (i=0;i<this->cpucount;i++) cpu[i]->Reset();
//		for (i=0;i<this->sndcount;i++) snd[i]->Reset();
}

/*
** shutdown: Removes all devices from the current hardware configuration,
** 	     leaving us with nothing. Usually called on exit.
*/
void CHardware::Shutdown(void) {
	int i=0;

	// Timers have no additional resources... we can just delete them
	for (i=0;i<this->timercount;i++) {
		delete[] this->timer[i];
	}

	// Now we have to do device-specific cleanups
	for (i=0;i<this->cpucount;i++) {
	//	cpu[i]->Shutdown();
		delete[] cpu[i];
	}
}
